﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.IO;
using Utilities;

namespace Communication.Packets
{
    public class PacketTypeNotFoundError : Exception
    {
    }

    /// <summary>
    /// Singleton utility to find Packet types by buffer.
    /// </summary>
    public sealed class PacketFinder
    {
        #region Singleton
        private static volatile PacketFinder m_instance;
        private static object m_mutex = new Object();

        public static PacketFinder Instance
        {
            get
            {
                if (m_instance == null)
                {
                    lock (m_mutex)
                    {
                        if (m_instance == null)
                            m_instance = new PacketFinder();
                    }
                }

                return m_instance;
            }
        }
        #endregion

        #region Ctor
        /// <summary>
        /// C-tor, initializes the packet types structure from reflection.
        /// </summary>
        private PacketFinder()
        {
            Logger.Instance.Log("Initializing...");
            this.m_packets = new Dictionary<Type, List<int>>();

            // Begin search in the entire solution for packets
            foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
            {
                foreach (Type type in assembly.GetTypes())
                {
                    if (type.IsSubclassOf(typeof(Packet)) && type.IsSealed)
                    {
                        // found packet type
                        // create the ID routing data
                        Stack<int> ids = new Stack<int>();
                        Type tempType = type;
                        do
                        {
                            ids.Push((int)tempType.GetField("ID").GetValue(null));
                            tempType = tempType.BaseType;
                        } while (tempType != typeof(Packet));
                        this.m_packets.Add(type, new List<int>(ids));
                    }
                }
            }
            Logger.Instance.Log("Initialized.");
        }
        #endregion

        #region Methods
        /// <summary>
        /// Gets the packet type from a buffer
        /// </summary>
        /// <param name="buffer">The buffer to read the packet from</param>
        /// <returns>The type of the packet</returns>
        public Type GetPacketType(BufferedStream buffer)
        {
            // skip header
            PacketType type = (PacketType)buffer.ReadByte();
            int position = 2;
            if (type == PacketType.C2 || type == PacketType.C4)
            {
                position++;
            }
            buffer.Seek(position, SeekOrigin.Begin);

            // search for the currect type by the ID routing.
            foreach (KeyValuePair<Type, List<int>> pair in this.m_packets)
            {
                bool found = true;
                // start to try the current routing.
                foreach (int id in pair.Value)
                {
                    if (buffer.ReadByte() != id)
                    {
                        found = false;
                        break;
                    }
                }
                if (found)
                {
                    // return to begin
                    buffer.Seek(0, SeekOrigin.Begin);
                    return pair.Key;
                }
                buffer.Seek(position, SeekOrigin.Begin);
            }

            buffer.Seek(0, SeekOrigin.Begin);
            throw new PacketTypeNotFoundError();
        }

        #endregion

        #region Fields
        private Dictionary<Type, List<int>> m_packets;
        #endregion
    }
}
